#include "HikCamera.h"
#include <unistd.h>

bool PrintDeviceInfo(MV_CC_DEVICE_INFO *pstMVDevInfo)
{
  if (NULL == pstMVDevInfo)
  {
    printf("The Pointer of pstMVDevInfo is NULL!\n");
    return false;
  }
  if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE)
  {
    int nIp1 =
        ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24);
    int nIp2 =
        ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16);
    int nIp3 =
        ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8);
    int nIp4 = (pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff);
    // ch:打印当前相机ip和用户自定义名字 | en:print current ip and user defined
    // name
    printf("Device Model Name: %s\n",
           pstMVDevInfo->SpecialInfo.stGigEInfo.chModelName);
    printf("CurrentIp: %d.%d.%d.%d\n", nIp1, nIp2, nIp3, nIp4);
    printf("UserDefinedName: %s\n\n",
           pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);
  }
  else if (pstMVDevInfo->nTLayerType == MV_USB_DEVICE)
  {
    printf("Device Model Name: %s\n",
           pstMVDevInfo->SpecialInfo.stUsb3VInfo.chModelName);
    printf("UserDefinedName: %s\n\n",
           pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName);
  }
  else
  {
    printf("Not support.\n");
  }
  return true;
}

bool HikCamera::CameraInit()
{
  std::lock_guard<std::mutex> lock(mutex_);
  do
  {
    memset(&this->m_stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
    // 枚举设备
    // enum device
    this->m_nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE,
                                     &this->m_stDeviceList);
    if (MV_OK != this->m_nRet)
    {
      std::cerr << "MV_CC_EnumDevices fail! this->m_nRet [" << this->m_nRet << "]" << std::endl;
      return false;
    }
    if (this->m_stDeviceList.nDeviceNum > 0)
    {
      for (int i = 0; i < this->m_stDeviceList.nDeviceNum; i++)
      {
        logMessage("[device  " + std::to_string(i) + "]:");
        MV_CC_DEVICE_INFO *pDeviceInfo = this->m_stDeviceList.pDeviceInfo[i];
        if (NULL == pDeviceInfo)
        {
          break;
        }
        PrintDeviceInfo(pDeviceInfo);
      }
    }
    else
    {
      logMessage("Find No Devices!");
      sleep(2);
      return false;
    }

    // 选择设备并创建句柄
    // select device and create this->m_handle
    this->m_nRet = MV_CC_CreateHandle(&this->m_handle,
                                      this->m_stDeviceList.pDeviceInfo[nIndex]);
    if (MV_OK != this->m_nRet)
    {
      printf("MV_CC_CreateHandle fail! this->m_nRet [%x]\n", this->m_nRet);
      return false;
    }
    // 打开设备
    // open device
    this->m_nRet = MV_CC_OpenDevice(this->m_handle);
    if (MV_OK != this->m_nRet)
    {
      printf("MV_CC_OpenDevice fail! this->m_nRet [%x]\n", this->m_nRet);
      return false;
    }
  } while (0);
  return true;
}

bool HikCamera::SetWidthAndHeight(unsigned int Width, unsigned int Height)
{
  std::lock_guard<std::mutex> lock(mutex_);

  // 宽高设置时需考虑步进(16)，即设置宽高需16的倍数
  // Step (16) should be considered when setting width and height, that is the
  // width and height should be a multiple of 16
  this->m_nRet = MV_CC_SetIntValue(this->m_handle, "Width", Width);
  if (MV_OK == this->m_nRet)
  {
    std::cout << "set width OK!" << std::endl;
  }
  else
  {
    printf("set width failed! nRet [%x]\n", this->m_nRet);
    return false;
  }

  this->m_nRet = MV_CC_SetIntValue(this->m_handle, "Height", Height);
  if (MV_OK == this->m_nRet)
  {
    printf("set height OK!\n");
  }
  else
  {
    printf("set height failed! nRet [%x]\n", this->m_nRet);
    return false;
  }

  this->m_nRet = MV_CC_SetEnumValue(this->m_handle, "PixelFormat", 0x01080009);
  if (MV_OK != this->m_nRet)
  {
    printf("MV_CC_SetPixelFormatMode fail! nRet [%x]\n", this->m_nRet);
    return false;
  }

  return true;
}

// 设置相机偏移量
bool HikCamera::SetOffsetXAndOffsetY(unsigned int OffsetX,
                                     unsigned int OffsetY)
{
  std::lock_guard<std::mutex> lock(mutex_);

  this->m_nRet = MV_CC_SetIntValue(this->m_handle, "OffsetX", OffsetX);
  if (MV_OK == this->m_nRet)
  {
    printf("set OffsetX OK!\n");
  }
  else
  {
    printf("set OffsetX failed! nRet [%x]\n", this->m_nRet);
    return false;
  }

  this->m_nRet = MV_CC_SetIntValue(this->m_handle, "OffsetY", OffsetY);
  if (MV_OK == this->m_nRet)
  {
    printf("set OffsetY OK!\n");
  }
  else
  {
    printf("set OffsetY failed! nRet [%x]\n", this->m_nRet);
    return false;
  }
  return true;
}

bool HikCamera::StartGrabbing()
{
  std::lock_guard<std::mutex> lock(mutex_);

  do
  {
    // ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal
    // package size(It only works for the GigE camera)
    if (this->m_stDeviceList.pDeviceInfo[nIndex]->nTLayerType ==
        MV_GIGE_DEVICE)
    {
      int nPacketSize = MV_CC_GetOptimalPacketSize(this->m_handle);
      if (nPacketSize > 0)
      {
        this->m_nRet =
            MV_CC_SetIntValue(this->m_handle, "GevSCPSPacketSize", nPacketSize);
        if (this->m_nRet != MV_OK)
        {
          printf("Warning: Set Packet Size fail this->m_nRet [0x%x]!\n",
                 this->m_nRet);
        }
      }
      else
      {
        printf("Warning: Get Packet Size fail this->m_nRet [0x%x]!\n",
               nPacketSize);
      }
    }

    this->m_nRet = MV_CC_SetEnumValue(this->m_handle, "TriggerMode", 0);
    if (MV_OK != this->m_nRet)
    {
      printf("MV_CC_SetTriggerMode fail! this->m_nRet [%x]\n", this->m_nRet);
      break;
    }
    // ch:获取数据包大小 | en:Get payload size
    memset(&this->m_stParam, 0, sizeof(MVCC_INTVALUE));
    this->m_nRet =
        MV_CC_GetIntValue(this->m_handle, "PayloadSize", &this->m_stParam);
    if (MV_OK != this->m_nRet)
    {
      printf("Get PayloadSize fail! this->m_nRet [0x%x]\n", this->m_nRet);
      break;
    }
    // 开始取流
    // start grab image
    this->m_nRet = MV_CC_StartGrabbing(this->m_handle);
    if (MV_OK != this->m_nRet)
    {
      printf("MV_CC_StartGrabbing fail! this->m_nRet [%x]\n", this->m_nRet);
      return false;
    }
  } while (0);
  return true;
}

cv::Mat HikCamera::GetOneFrame()
{
  std::lock_guard<std::mutex> lock(mutex_);

  unsigned char *pDataForRGB = NULL;
  cv::Mat img;
  do
  {
    memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));
    unsigned int nDataSize = this->m_stParam.nCurValue;
    if (pData)
    {
      free(pData);
      pData = NULL;
    }
    pData = (unsigned char *)malloc(sizeof(unsigned char) *
                                    this->m_stParam.nCurValue);
    if (NULL == pData)
    {
      std::cout << "create pData fail!" << std::endl;
      break;
    }

    this->m_nRet = MV_CC_GetOneFrameTimeout(this->m_handle, pData, nDataSize,
                                            &stImageInfo, 1000);
    if (this->m_nRet == MV_OK)
    {
      // printf("Now you GetOneFrame, Width[%d], Height[%d], nFrameNum[%d]\n\n",
      //        stImageInfo.nWidth, stImageInfo.nHeight, stImageInfo.nFrameNum);
      // 处理图像
      // image processing
      pDataForRGB = (unsigned char *)malloc(
          stImageInfo.nWidth * stImageInfo.nHeight * 4 + 2048);
      if (NULL == pDataForRGB)
      {
        break;
      }
      // 像素格式转换
      // convert pixel format
      MV_CC_PIXEL_CONVERT_PARAM stConvertParam = {0};
      // 从上到下依次是：图像宽，图像高，输入数据缓存，输入数据大小，源像素格式，
      // 目标像素格式，输出数据缓存，提供的输出缓冲区大小
      // Top to bottom are：image width, image height, input data buffer, input
      // data size, source pixel format, destination pixel format, output data
      // buffer, provided output buffer size
      stConvertParam.nWidth = stImageInfo.nWidth;
      stConvertParam.nHeight = stImageInfo.nHeight;
      stConvertParam.pSrcData = pData;
      stConvertParam.nSrcDataLen = stImageInfo.nFrameLen;
      stConvertParam.enSrcPixelType = stImageInfo.enPixelType;
      stConvertParam.enDstPixelType = PixelType_Gvsp_RGB8_Packed;
      stConvertParam.pDstBuffer = pDataForRGB;
      stConvertParam.nDstBufferSize =
          stImageInfo.nWidth * stImageInfo.nHeight * 4 + 2048;
      this->m_nRet = MV_CC_ConvertPixelType(this->m_handle, &stConvertParam);
      if (MV_OK != this->m_nRet)
      {
        printf("MV_CC_ConvertPixelType fail! this->m_nRet [%x]\n",
               this->m_nRet);
        break;
      }

      // 创建 cv::Mat 对象
      img = cv::Mat(stImageInfo.nHeight, stImageInfo.nWidth, CV_8UC3, pDataForRGB)
                .clone();
    }
  } while (0);
  if (pDataForRGB)
  {
    free(pDataForRGB);
    pDataForRGB = NULL;
  }

  return img;
}

bool HikCamera::writeForSaveImage(std::string file_path)
{
  std::lock_guard<std::mutex> lock(mutex_);

  unsigned char *pDataForSaveImage = NULL;
  pDataForSaveImage = (unsigned char *)malloc(
      stImageInfo.nWidth * stImageInfo.nHeight * 4 + 2048);
  if (NULL == pDataForSaveImage)
  {
    return false;
  }
  // 填充存图参数
  // fill in the parameters of save image
  MV_SAVE_IMAGE_PARAM_EX stSaveParam;
  memset(&stSaveParam, 0, sizeof(MV_SAVE_IMAGE_PARAM_EX));
  // 从上到下依次是：输出图片格式，输入数据的像素格式，提供的输出缓冲区大小，图像宽，
  // 图像高，输入数据缓存，输出图片缓存，JPG编码质量
  // Top to bottom are：
  stSaveParam.enImageType = MV_Image_Bmp;
  stSaveParam.enPixelType = stImageInfo.enPixelType;
  stSaveParam.nBufferSize = stImageInfo.nWidth * stImageInfo.nHeight * 4 + 2048;
  stSaveParam.nWidth = stImageInfo.nWidth;
  stSaveParam.nHeight = stImageInfo.nHeight;
  stSaveParam.pData = pData;
  stSaveParam.nDataLen = stImageInfo.nFrameLen;
  stSaveParam.pImageBuffer = pDataForSaveImage;
  stSaveParam.nJpgQuality = 80;
  this->m_nRet = MV_CC_SaveImageEx2(this->m_handle, &stSaveParam);
  if (MV_OK != this->m_nRet)
  {
    printf("failed in MV_CC_SaveImage,this->m_nRet[%x]\n", this->m_nRet);
    return false;
  }

  FILE *fp = fopen(file_path.c_str(), "wb");
  if (NULL == fp)
  {
    printf("fopen failed\n");
    return false;
  }
  fwrite(pDataForSaveImage, 1, stSaveParam.nImageLen, fp);
  fclose(fp);
  free(pDataForSaveImage);
  printf("save image succeed\n");
  return true;
}

void HikCamera::StopGrabbing()
{
  std::lock_guard<std::mutex> lock(mutex_);

  do
  {
    // 停止取流
    // end grab image
    this->m_nRet = MV_CC_StopGrabbing(this->m_handle);
    if (MV_OK != this->m_nRet)
    {
      printf("MV_CC_StopGrabbing fail! this->m_nRet [%x]\n", this->m_nRet);
      break;
    }
  } while (0);
}

void HikCamera::CloseCamera()
{
  std::lock_guard<std::mutex> lock(mutex_);

  // 销毁句柄
  // destroy handle
  this->m_nRet = MV_CC_DestroyHandle(this->m_handle);
  if (MV_OK != this->m_nRet)
  {
    printf("MV_CC_DestroyHandle fail! this->m_nRet [%x]\n", this->m_nRet);
  }
}

HikCamera::~HikCamera()
{

  if (this->m_handle != NULL)
  {
    MV_CC_DestroyHandle(this->m_handle);
    this->m_handle = NULL;
  }

  if (pData)
  {
    free(pData);
    pData = NULL;
  }
}
